package org.siam.impl.data {

	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	
	import mx.core.ClassFactory;
	
	import org.siam.core.data.IDataManager;
	import org.siam.utils.ClassUtils;
	import org.siam.utils.RuntimeError;
	import org.siam.utils.UIComponentFactory;
	
	public class DataManagerXMLHandler {
		
		public static const UICOMPONENT_CLASS:Class = ClassUtils.resolveName("mx.core.UIComponent");
		
		private static const SUPPORTED_TYPES:Object = {
			String: true, Object: true , Boolean: true, uint: true, int: true, Number: true 
		};
		
		private var _managerType:Class;
		private var _validType:Class;
		private var _objectName:String;
		private var _metaTypes:Object;
		private var _hasObjectFunction:Function;
		private var _registerObjectFunction:Function;
		
		public function DataManagerXMLHandler(
							managerType:Class,
							validType:Class, 
							objectName:String,
							metaTypes:Object) {
								
			_managerType = managerType;
			_validType = validType;
			_objectName = objectName;
			_metaTypes = metaTypes;
		}
		
		public function get managerType():Class {
			return _managerType;
		}
		
		public function get validType():Class {
			return _validType;
		}
		
		public function get objectName():String {
			return _objectName;
		}
		
		private function get metaTypes():Object {
			return _metaTypes;
		}
		
		protected function read(input:XML):IDataManager {
			var instance:DataManager = new managerType();
			if (input) {
				for each (var classXML:XML in input["class"]) {
					// Validates class name
					var objectClassName:String = classXML["@name"];
					if (!objectClassName) {
						throw new RuntimeError("A {0} class must have a valid name", 
												objectName);
					}
					// Try to resolve class
					var objectClass:Class = null;
					try {
						objectClass = ClassUtils.resolveName(objectClassName);
						if (!ClassUtils.isSubclass(objectClass, validType)) {
							throw new RuntimeError("The {0} class '{1}' is not a valid type. " +
												   "It must derive from '{2}'", 
												   objectName, objectClassName, getQualifiedClassName(validType));
						}
					} catch (e:ReferenceError) {
						throw new RuntimeError("The {0} class '{1}' could not be resolved. " + 
											   "Check that you typed the class name correcly. " + 
											   "If so, make sure to import it first in your application", 
											   objectName, objectClassName);
					} 	
					var classDescXML:XML = describeType(objectClass);
					var accessorsXML:Object = new Object();
					// Stores accessors
					for each (var accessorXML:XML in classDescXML.factory.accessor) {
						accessorsXML[accessorXML["@name"]] = accessorXML;
					}
					// Stores variables
					for each (var variableXML:XML in classDescXML.factory.variable) {
						accessorsXML[variableXML["@name"]] = variableXML;
					}
					for each (var objectXML:XML in classXML[objectName]) {
						// validates object id
						var objectId:String = objectXML["@id"];
						if (!objectId) {
							throw new RuntimeError("A {0} must have a valid id (class '{1}')", 
													objectName, objectClassName);
						} else if (instance.has(objectId)) {
							throw new RuntimeError("Duplicated {0} id '{1}' (class '{2}')", 
													objectName, objectId, objectClassName);
						}
						// factory metadata
						var factoryMetadata:Object = new Object();
						// creates object using metadata
						var objectProperties:Object = new Object();
						var objectStyles:Object = new Object();
						var objectInstance:Object = new objectClass(); 
						var hasStyles:Boolean = objectInstance is UICOMPONENT_CLASS;
						if (hasStyles) {
							objectInstance.regenerateStyleCache(false);
						}
						for each (var attributeXML:XML in objectXML.attributes()) {
							var key:String = attributeXML.name();
							var value:String = attributeXML.toString();
							switch (key) {
								case "id": break; // Already set
								default:
									if (metaTypes.hasOwnProperty(key)) { // Meta-keys
										var metaKeyClass:Class = metaTypes[key];
										factoryMetadata[key] = parse(value, metaKeyClass); 
									} else { /* Accessors */
										accessorXML = accessorsXML.hasOwnProperty(key) ? accessorsXML[key] : null;
										if (!accessorXML) {
											if (hasStyles) {
												var defaultStyleValue:* = objectInstance.getStyle(key);
												if (defaultStyleValue == undefined) {
													throw new RuntimeError("The accessor, variable or style '{0}' for {1} class '{2}' could not be found", 
																	key, objectName, objectClassName);
												} else {
													var styleClass:Class = defaultStyleValue.constructor;
													objectStyles[key] = parse(value, styleClass);
												}
											} else {
												throw new RuntimeError("The accessor or variable '{0}' for {1} class '{2}' could not be found", 
												 					key, objectName, objectClassName);
											}
										} else if (accessorXML["@access"] == "readonly") {
											throw new RuntimeError("The accessor '{0}' for {1} class '{2}' is read-only!", 
																	key, objectName, objectClassName);
										} else {
											var accessorTypeName:String = accessorXML["@type"];
											if (!SUPPORTED_TYPES.hasOwnProperty(accessorTypeName)) {										
												throw new RuntimeError("The accessor '{0}' type '{1}' for {2} class '{3}' is not valid!"
																		+ "Only Object and primitive String, Boolean, int, uint, Number are so far supported...", 
																		key, accessorTypeName, objectName, objectClassName);
											}
											var accessorClass:Class = ClassUtils.resolveName(accessorTypeName);
											objectProperties[key] = parse(value, accessorClass);
										}
									}
									break;
							}
						}
						var factory:ClassFactory = hasStyles ? new UIComponentFactory(objectClass, objectStyles)
															: new ClassFactory(objectClass);
						factory.properties = objectProperties;
						instance.register(objectId, factory, factoryMetadata);
					}
				}
				if (!instance.has(DataManager.DEFAULT_ID)) {
					throw new RuntimeError("A 'default' {0} must be declared", objectName);
				}
			}
			return instance;
		}
		
		private function parse(value:String, toType:Class):* {
			// Conversion from String to Boolean:
			// => false if the value is null or the empty string (""); true otherwis
			// http://livedocs.adobe.com/flex/3/html/help.html?content=03_Language_and_Syntax_12.html
			if (toType == Boolean && value == "false") {
				return false;
			}
			if (value != null && toType == Array || toType == Object) {
				if (value.indexOf("[") == 0 && value.lastIndexOf("]") == value.length-1) {
					return value.substr(1, value.length-2).split(",");
				}
			}
			
			return toType(value);
		}
	}
}